RustでRay Tracing in One Weekend (アニメーション付き)
https://youtu.be/xqwVpMg6__8
GitHub リポジトリ
https://gh-card.dev/repos/nwtgck/ray-tracing-iow-rust.svg https://github.com/nwtgck/ray-tracing-iow-rust
原本・訳本
Ray Tracing in One Weekend のあんどうやすし さんの日本語訳のRay Tracing in One Weekend のRust実装。達人出版で入手できる。とても読みやすくて良いで色んな人が色んな言語で実装している。 技術的な話とかこだわりとか
レイトレーシングは独立してピクセル計算が可能
一つ一つのピクセルを並列処理して高速に画像を生成する
アンチエイリアシングのための平均をとる部分も並列処理される
アニメーションのときは各フレームも並列処理される
アニメーション
カメラワークもつけてる(sin, cosを使って回るようなカメラワーク)
球体は鉛直投げ上げで少し物理の計算が入っている
球体個々で質量や初速度が異なり、一個ずつ落ちるスピードなどが異なる
1枚ずつフレームごとに画像を生成して、パラパラ漫画のようffmpegでくっつけて動画にしている
SceneというCameraとHitableの組のイテレータさえ作れば、自分好みのカメラワークと物体でアニメーションができる
再現的な乱数を使っている
参照透過性(冪等性)が好きだから
冪等性があるほうがデバッグするときもしやすいし、リファクタリングしたときに結果にdiffがないことを確かめやすいなどのメリットがある
シードを固定すれば何度やってもピクセル単位で同じ画像が生成できる
並列処理と組み合わせるため、ピクセルごとに予めシードを用意するなどの工夫をしている
StructOptでオプションのパースをしている
Clapしようかと思ったがオプションに型がつかなさそうなのと、StructOptが内部でClapを使っていて、なおかつREADME見ると上手くstructにオプションを落とせて、文字列でオプションを取り出したりして、エラーハンドリングが大変になりそうな気配がなかったため利用した。実際に現在の利用では大変でなかった。
フレームをステップごとにスキップ
254e13a46bfc7edf702896f923e1a2c9e9f711e5でついた機能
--anime-skip-stepで指定可能(0にすればスキップなしの意味になる)
最大の目的は--anime-dtを大きくすると、球体が地面にめり込んでしまうため、dtは大きくしないで、フレーム数を間引きしたい欲求を満たす仕組み
実装的には、任意のIteratorを特定のステップ感覚でスキップしたIteratorにしている。汎用性はあると思う
ピクセル数について - 計12902400000ピクセル
1枚の画像が1280x720で921600ピクセルある。アンチエイリアシングの目的で、同じピクセルに70回少しずらしてレイを飛ばしている。つまり921600 x 70=64512000ピクセルにレイを飛ばすことになる。アニメーションにするために、それを200枚で集めるそのため、64512000x200 = 12902400000。つまり、最終的に12902400000回に向かってレイを飛ばして、上記のYouTube動画の画質になっている。
(追記:更新後の動画では200フレームから172フレームに変化しため、少し減る)
どこがボトルネックか?
なぜ作ったか?
Rustに慣れていくため。Rust実装でPiping Severの転送速度を1.7倍~1.8倍高速化 (Hyper)のPiping Serverとは違ったプログラムを書いてRustに慣れていくため。レイトレーシングを選んだのは並列処理の入る余地があるから。Rustの設計思想は並列処理を意識して作られている部分が多くあるので、並列処理に親しめると思った。並列処理部分を何か改善したり試したりするときにこのレイトレーシングが今後使えると思う。あとはGCのないRustで書いたときに、Scala版と比べてどれだけ高速にレンダリングできるのかに興味があった。上記のレイトレーシングはRayonによる並列処理や演算子のオーバーロード(std::opts)やコマンドラインパーサーなどが使われていて、そういう分野での経験を得ることが少しできた。 下記のScala版を以前作っていた。これを参考にRust版を作っている。ある程度構成などが似た感じになっている。